!lm10
!rm76
Don't Be Shiftless

Now for another article aimed at that half of you who are really new to 6502 assembly language!

Sliding the bits in a byte back and forth, to the left or the right, is one of the traditional things computers like to do.  Big computers have fancy instructions for doing it in many different ways, with special effects along the way.  The 6502 only has four "shift" opcodes, so we have to work harder to get all the types of shifting our programs need.

Why shift anything?  For various reasons, to suit your fancy.  Since data in a byte is normally construed as a binary number, a shift left one bit-position will double the value and a shift right one bit-position will halve the value.  If it is important to isolate a particular bit field out of a byte, and then to left or right justify the value which was stored in that field so that testing or arithmetic can be performed, you need shifting instructions.  In order to implement multiply and divide on the 6502 you need shifting instructions.  To position data for insertion into a bit field within a byte you need to shift.  And more.

Show me a picture of a shift.  Well, the 6502 makes that easy, because it is limited to shifting a byte to the left or the right, one bit-position at a time.

First let's look at the LSR instruction, which shifts right one bit-position.  "LSR" stands for "Logical Shift Right".  LSR will shift the contents of a byte one bit-position to the right, like this:

     Old value:   1 0 0 1 1 1 0 1

     <Do LSR>

     New value:   0 1 0 0 1 1 1 0

LSR shifts in a zero-bit on the left end; the bit that is shifted out the right end goes into the CARRY status bit.

In the sample above the binary value of the old byte is $9D in hex, or 157 decimal.  After shifting, the value is $4E hex or 78 decimal (157/2 = 78.5).

The fact the the bit shifted "out" goes into the CARRY status bit makes it possible to test what that bit was.  For example, if you need to test a byte to see if it is even or odd, you can LSR it once and then do BCC or BCS to test the carry bit.  If carry is set, the number was odd; if clear, it was even.  The bit stored in CARRY can have other uses we will discover later.

Now let's see the ASL ("Arithmetic Shift Left") do its thing.  It will shift a byte one bit-position to the left, with a zero coming in the right end.  The bit shifted out the left end goes into the CARRY status bit.  See the similarity to the LSR instruction?

     Old value:  0 0 0 1 1 1 0 1

     <Do ASL>

     New value:  0 0 1 1 1 0 1 0

Note that the value is doubled; $1D (29) became $3A (58).  This will not always be true; if the bit shifted out was a 1-bit, it will be doubled modulo 256.  Integer BASIC users will know what that means, because they have the MOD function.  For Applesoft-only people, it will mean here that the result is 256 less than the doubled value should be.  Let's see an example:  shifting 10011101 with ASL produces 00111010; $9D (157) becomes $3A (58), which is 256 less than 2*157.

More about the carry bit.  Suppose I want to see if the third bit in a byte is 1 or 0.  If the bit positions are numbered left to right from 7 down to 0 (like this: 7 6 5 4 3 2 1 0), I want to test bit 5.  If I do three ASL's in a row, bit 5 will be in the CARRY status bit, and I can test it.  Or, I could do two ASL's in a row, and look at the MINUS status bit.  After a shift, the MINUS status bit is set if the new bit 7 is a 1-bit, or cleared if bit 7 is a 0-bit.  The BPL and BMI instructions test the MINUS status bit.

There are two more shift instructions to look at:  ROL and ROR.  "ROL" is pronounced like a type of bread you eat at dinner, and "ROR" like the noise those giant cats at the zoo make.  "ROL" stands for "Rotate One Left"; "ROR" means "Rotate One Right".  They work just like LSR and ASL, except for what is shifted in to the byte.  LSR shifts a zero-bit in the left end, and ASL shifts a zero-bit in the right end.  ROL and ROR shift the old CARRY status bit in, just before the shifted-out bit comes into the CARRY bit.

                      Byte         Carry
     Old value:  1 0 0 1 1 1 0 1     1

     <Do ROL>

     New value:  0 0 1 1 1 0 1 1     1

     <Do ROL>

     New value:  0 1 1 1 0 1 1 1     0

     <Do ROL>

     New value:  1 1 1 0 1 1 1 0     0

     <Do ROR>

     New value:  0 1 1 1 0 1 1 1     0

     <Do ROR>

     New value:  0 0 1 1 1 0 1 1     1

     <Do ROR>

     New value:  1 0 0 1 1 1 0 1     1

     <Do ROR>

     New value:  1 1 0 0 1 1 1 0     1


What about shifting values which take two bytes?  We can do it using combinations of the four opcodes.  Suppose you want to shift a 16-bit value stored at $1234 and $1235 left one bit-position.  You want a zero to enter the least significant bit position, which is bit 0 of $1234.  You want the most significant bit, bit 7 of $1235, to be in CARRY when you are through.  Here is the program:

     ASL $1234    0 --> bit 0, bit 7 --> CARRY
     ROL $1235    CARRY --> bit 0, then bit 7 into CARRY

Simple, isn't it!

Addressing Modes.  The four shift instructions all have the same five addressing modes.  There is a one-byte form which shifts the A-register.  Some assemblers write this as "ASL A", and don't allow "A" to be used as a label elsewhere.  The S-C ASSEMBLER II writes it as just "ASL", so you can use "A" as a label elsewhere if you wish.  The other addressing modes are:  zero page direct; zero page,X; absolute; and absolute,X.  No indirect modes, or indexing by Y modes are available.

[If you remember the article a few months ago about the "secret" opcodes, you will also remember that the two indirect-indexed modes and the absolute,Y mode are available if you don't mind what happens to the A-register after the shift.  Or, if what does happen is something you also wanted.  You might look up the article.]

Some real examples.  The Apple Monitor ROM has some good examples in it.  Disassemble (or look in the Monitor listing in the Reference Manual) at $FBC1 (the BASCALC subroutine.  If you have the old Monitor ROMs, the multiply and divide subroutines at $FB60 and $FB81 are good examples.  The PRBYTE subroutine at $FDDA uses four LSR's to get at the first hex digit.  The subroutine DIG at $FF8A is used to convert ascii hex numbers to binary.  Let's look at that one here:

!lm15
FF8A: A2 03     DIG    LDX #$03   LOOP 4 TIMES
FF8C: 0A               ASL        LEFT JUSTIFY DIGIT VALUE
FF8D: 0A               ASL
FF8E: 0A               ASL
FF8F: 0A               ASL
FF90: 0A        NXTBIT ASL        SHIFT DIGIT INTO A2L,A2H
FF91: 26 3E            ROL A2L
FF93: 26 3F            ROL A2H
FF95: CA               DEX
FF96: 10 F8            BPL NXTBIT

!lm10
The ASCII value of the hex digit has already been modified so that the digit's value is in bits 3-0.  The first four ASL's shift those 4 bits up to bits 7-4.  The next ASL shifts the top bit into CARRY, and then the two ROL's shift that bit into the 16-bit value at A2L and A2H.  The ASL-ROL-ROL loop is done four times, so all four bits are shifted into A2L,A2H.

In the Applesoft ROMs there is a subroutine which shifts a 32-bit value right any number of bit-positions.  The subroutine is used in the floating point arithmetic package to adjust mantissas.  It has the interesting feature (for speed's sake) of shifting 8 bits at a time until the shift count is less than 8.  This is done by moving bytes with LDY-STY pairs.  The code is at $E8DC thru $E912.  The normal entry point is at $E8F0, with the number of bit-positions to be shifted in the A-register as a negative number, and with CARRY clear.  The code above $E8F0 shifts right by bytes, and the code after $E8F0 shifts right by bits.  The data to be shifted is in page zero, offset by the value in the X-register.

A somewhat similar subroutine is used to normalize the mantissa after a calculation.  "Normalize" means to shift the mantissa left until the most significant bit is a one-bit.  This code is at $E82E-E854 and $E874-E880.  The first portion shifts left by bytes until the leading byte is non-zero (or until it has been determined that the whole value is zero).  Once the leading byte is found to be non-zero, the second portion of code shifts left by bits until the leading bit is 1.  The number of bit-positions shifted is counted as the subroutine moves along, and that value is subtracted from the exponent value of the floating point number ($E882-E88B).

Disassemble the routines I have pointed out in the various ROMs, and study them a while.  Then try writing some of your own examples.  Here is an assignment:  write a subroutine that will shift a 16-bit value left or right from 0-15 bit positions.  The value to be shifted is in page zero at $9D and $9E.  The shift count is in the A-register.  If the value in A is zero, return without doing anything.  If A is negative, it indicates a shift right.  If A is positive, it means to shift left.  Okay?  Give it a try!
